package nl.Viewer.ViewManager.Models;

import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;

import nl.Viewer.SerializedObjects.Parameter;
import nl.Viewer.SerializedObjects.ParameterValue;
import nl.Viewer.ViewManager.Models.DataStores.DataStore;
import nl.Viewer.ViewManager.Models.ParameterStores.ParameterModel;

/**
 * A Data Model according to the Model object in the Model-View-Controller paradigm. This datamodel is responsible for
 * storing/retrieving/maintaining all data-related structures within a visualization session
 * 
 * @author Jeroen Verschuur
 * 
 */

public class DataModel {
   protected DataStore                             dataStore;
   protected ParameterModel                        parameterModel;

   private ArrayList<DataStoreModelChangeListener> dataStoreChangeListeners = new ArrayList<DataStoreModelChangeListener>();
   private ArrayList<ParameterModelChangeListener> parameterChangeListeners = new ArrayList<ParameterModelChangeListener>();

   public DataModel() {
      this.dataStore = null;
      this.parameterModel = new ParameterModel();
   }

   public void cleanup() {
      if( dataStore != null ) {
         dataStore.cleanup();
      }
      if( parameterModel != null ) {
         parameterModel.cleanup();
      }
   }

   /**
    * Listener operations
    * 
    */

   public void addDataStoreModelChangeListener( DataStoreModelChangeListener listener ) {
      synchronized( dataStoreChangeListeners ) {
         if( !dataStoreChangeListeners.contains( listener ) )
            dataStoreChangeListeners.add( listener );
      }
   }

   private void notifyDataStoreModelChangeListeners( boolean newDataStore ) {
      synchronized( dataStoreChangeListeners ) {
         for( DataStoreModelChangeListener l : dataStoreChangeListeners ) {
            l.dataStoreChanged( newDataStore );
         }
      }
   }

   /**
    * Add a listener for changes to the parameter model data
    * 
    * @param listener
    */
   public void addParameterModelChangeListener( ParameterModelChangeListener listener ) {
      synchronized( parameterChangeListeners ) {
         if( !parameterChangeListeners.contains( listener ) )
            parameterChangeListeners.add( listener );
      }
   }

   private void notifyParameterModelChangeListeners( ArrayList<Parameter> updatedParameters ) {
      synchronized( parameterChangeListeners ) {
         for( ParameterModelChangeListener l : parameterChangeListeners ) {
            for( Parameter p : updatedParameters ) {
               l.parameterValueChanged( p );
            }
         }
      }
   }

   private void notifyParameterModelChangeListeners( Parameter updatedParameter ) {
      synchronized( parameterChangeListeners ) {
         for( ParameterModelChangeListener l : parameterChangeListeners ) {
            l.parameterValueChanged( updatedParameter );
         }
      }
   }

   /**
    * Retrieve the current datastore
    * 
    * @return
    */

   public DataStore getDataStore() {
      if( dataStore == null )
         return null;
      synchronized( dataStore ) {
         return dataStore;
      }
   }

   /**
    * Update data on the current datastore, given some resolution and horizontal and vertical position in the data
    * (regular images will use only 1 resolution and x/y pos, but zoomify datastores for example will use all the
    * parameters).
    * 
    * @param data
    * @param resolution
    * @param xPos
    * @param yPos
    */

   public void updateData( byte[] data, int resolution, int xPos, int yPos ) {
      if( dataStore == null ) {
         System.out.println( "A new datastore should be created before updating data " );
         return;
      }
      synchronized( dataStore ) {
         dataStore.addData( data, resolution, xPos, yPos );
      }
      notifyDataStoreModelChangeListeners( false );
   }

   /**
    * Remove the current datastore
    * 
    */

   public void clearDataStore() {
      if( dataStore != null ) {
         synchronized( dataStore ) {
            dataStore = null;
         }
      }
   }

   /**
    * Set a new datastore as the active datastore and notify all datastore change listeners (these are mostly the views
    * that need to know whether their image-to-display has changed
    * 
    * @param store
    */
   public synchronized void createNewDataStore( DataStore store ) {
      dataStore = store;
      notifyDataStoreModelChangeListeners( true );
   }

   /**
    * Parameter operations
    * 
    */

   public void setAllParametersModified() {
      synchronized( parameterModel ) {
         parameterModel.setAllParametersModified();
      }
   }

   /**
    * Return the list of active parameters for this datamodel
    * 
    */
   public ConcurrentHashMap<String, Parameter> getParameters() {
      synchronized( parameterModel ) {
         return parameterModel.getParameters();
      }
   }

   /**
    * Return a snapshot of all changed parameters. This method is used when requesting a render, to get a list of the
    * parameters to send to the server.
    * 
    * @return
    */

   public ArrayList<Parameter> getParameterSnapshot() {
      synchronized( parameterModel ) {
         return parameterModel.getParameterSnapshot();
      }
   }

   /**
    * This method will replace all the model parameters with a new set (usually only used once when the session is
    * created).
    * 
    * @param params
    */

   public void setParameters( ArrayList<Parameter> params ) {
      synchronized( parameterModel ) {
         parameterModel.setParameters( params );
      }
      notifyParameterModelChangeListeners( params );
   }

   /**
    * This method will update the parameters in the ParameterValue ArrayList specified, and notifies all listeners of
    * the changes. The updateRequiresRender is used for updates from the client, for which a new render is needed. Code
    * handling parameters updates from the server should NOT use 'true' in this case, as that will create a loop of
    * renders.
    * 
    * @param parameterValues
    * @param updateRequiresRender
    */

   public void updateParameterValues( ArrayList<ParameterValue> parameterValues, boolean updateRequiresRender ) {
      ArrayList<Parameter> updatedParameters;
      synchronized( parameterModel ) {
         updatedParameters = parameterModel.updateParameterValues( parameterValues, updateRequiresRender );
      }
      notifyParameterModelChangeListeners( updatedParameters );
   }

   /**
    * This method is used to add a single parameter to the parameter model. Currently used only for views that would
    * like to share parameters with other views
    */

   public void addParameter( Parameter p ) {
      parameterModel.addParameter( p );
      notifyParameterModelChangeListeners( p );
   }

   /**
    * This method will force a notification of a parameter update of a specified list of parameters
    * 
    * @param params
    */

   public void forceParameterUpdateNotification( Parameter... params ) {
      for( Parameter p : params ) {
         if( p != null )
            notifyParameterModelChangeListeners( p );
      }
   }

   /**
    * This method will force a notification of a parameter update of an ArrayList of parameters
    * 
    * @param params
    */

   public void forceParameterUpdateNotification( ArrayList<Parameter> params ) {
      for( Parameter p : params ) {
         if( p != null )
            notifyParameterModelChangeListeners( p );
      }
   }

}
